#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk
import string
import keyword
import exceptions
import pango
import os
import sys
import re
import new
import cairo
from traceback import *
#from gtkcodebuffer import CodeBuffer, SyntaxLoader, add_syntax_path
import webbrowser
import gtksourceview2 as gtksourceview

##
# TODO:
# * Put focus on editor after 'new' command ( which other commands? )
# * Put sizer on the middle
# * Editor Functions:
#    * Search and replace
#    * Delete line
#    * Indent
#    * Save as  doesn't work
# Remove moo references
# Put defualt color in locals
# Clean console
# Make error reporting more intuitive ( Some kind ok color indication in the status bar )
# Use differentes colors in the console highlighning past and current outputs
# Make easy the use of color
# Make easy to acces cairo doc
# Make easy to acces color reference (color picker)
# get rid of all pass lines
# Put controls on the canvas area the allow the user to scroll and zoom into the image



DEF_CAIRO_CODE = \
"""#Cairo canvas. The following variables are available:
# * CNTX: A reference to the current cairo context object
# * W: Canvas width
# * H: Canvas height
# Have fun!
"""


def GLOBAL_COLOR( color_name ):
    col = gtk.gdk.color_parse( color_name )
    return ( col.red, col.green, col.blue )

def GLOBAL_COLOR_NORM( color_name ):
    col = gtk.gdk.color_parse( color_name )
    return ( float(col.red) / 65535, float(col.green) / 65535 , float(col.blue) / 65535 )

def comment(sourceview):
    """
    """
    sourceview.set_property("editable", False)
    sourcebuffer = sourceview.get_property("buffer")
    if sourcebuffer.get_has_selection():
        begin, end = sourcebuffer.get_selection_bounds()
        first_line = begin.get_line()
        last_line = end.get_line()
    else:
        end = None
        first_line = get_curr_iter(sourcebuffer).get_line()
        last_line = first_line

    if ( first_line != last_line ):
        end.get_line_offset() == 0;
        last_line = last_line - 1

    sourcebuffer.begin_user_action()
    for line in range(first_line, last_line+1):
        result = comment_line(sourceview, line, end )

    sourcebuffer.end_user_action()
    sourceview.set_property("editable", True)
    pass

def comment_line(sourceview, line_number, end_iter ):
    """
    Inserts one '#' at the beginnig of the line
    """
    sourcebuffer = sourceview.get_property("buffer")
    begin_position = sourcebuffer.get_iter_at_line(line_number)
    sourcebuffer.insert(begin_position, '#')
    pass

def uncomment(sourceview):
    """
    """
    sourceview.set_property("editable", False)
    sourcebuffer = sourceview.get_property("buffer")
    if sourcebuffer.get_has_selection():
        begin, end = sourcebuffer.get_selection_bounds()
        first_line = begin.get_line()
        last_line = end.get_line()
    else:
        first_line = get_curr_iter(sourcebuffer).get_line()
        last_line = first_line

    if ( first_line != last_line ):
        end.get_line_offset() == 0;
        last_line = last_line - 1

    sourcebuffer.begin_user_action()
    for line in range(first_line, last_line+1):
        result = uncomment_line(sourceview, line)

    sourcebuffer.end_user_action()
    sourceview.set_property("editable", True)
    pass

def uncomment_line(sourceview, line_number):
    """
    Delete one '#' at the beginnig of the line ( If there's one )
    """
    sourcebuffer = sourceview.get_property("buffer")
    begin_position = sourcebuffer.get_iter_at_line(line_number)
    end_position = sourcebuffer.get_iter_at_line(line_number)
    if begin_position.get_char() != '#':
        return

    end_position.forward_char()
    sourcebuffer.delete( begin_position, end_position )
    pass

def delete_line( sourceview ):
    sourcebuffer = sourceview.get_property("buffer")
    begin_iter = sourcebuffer.get_iter_at_line( sourcebuffer.get_curr_iter().get_line() )
    curr_line = begin_iter.get_line()
    end_iter = sourcebuffer.get_iter_at_line(curr_line + 1)
    sourcebuffer.begin_user_action()
    sourcebuffer.delete( begin_iter, end_iter )
    sourcebuffer.end_user_action()
    pass

def get_curr_iter( buffer):
    curr_pos = buffer.get_property("cursor-position")
    return buffer.get_iter_at_offset(curr_pos)

def get_curr_line( buffer ):
    curr_iter = get_curr_iter( buffer )
    line = curr_iter.get_line()
    start_iter = buffer.get_iter_at_line(line)
    return buffer.get_text( start_iter, curr_iter )

def get_curr_line_iters( buffer ):
    curr_iter = get_curr_iter( buffer)
    line = curr_iter.get_line()
    start_iter = buffer.get_iter_at_line(line)
    end_iter = buffer.get_iter_at_line_offset(line, 4)
    #~ print "iter: %d, %d" % ( start_iter.get_offset(), end_iter.get_offset() )
    #~ print "%d, %d" % (line, buffer.get_line_count() - 1)
    #~ if line == buffer.get_line_count() - 1:
        #~ end_iter = buffer.get_end_iter()
    #~ else:
        #~ end_iter = buffer.get_iter_at_line_offset(line+1, -1)

    return ( start_iter, start_iter )

def insert_indented( text_view ):
    tab_width = text_view.get_property('tab-width')
    buffer = text_view.get_buffer()
    line = buffer.get_curr_line()
    # How many spaces at the beginning
    buffer.begin_user_action()
    buffer.insert_at_cursor("\n")
    line_len = len(line)
    trim_len = len(line.lstrip())
    if ( line_len <> trim_len):
        buffer.insert_at_cursor(line[0:line_len-trim_len])

    if (line.endswith(":")):
        buffer.insert_at_cursor('\t'.expandtabs(tab_width) )
        #~ buffer.insert_with_tags_by_name( buffer.get_curr_iter(), self.INDENT_STR, "bg_green")
    buffer.end_user_action()
    pass

def back_indented( text_view ):
    # Return to previous stop positon
    tab_width = text_view.get_property('tab-width')
    buffer = text_view.get_buffer()
    line = get_curr_line( buffer )
    line_len = len(line)
    curr_iter = buffer.get_curr_iter()
    curr_off = curr_iter.get_line_offset()
    if curr_off == 0:
        return False
    curr_line = curr_iter.get_line()

    #new_off = curr_off % 4
    prev_pos = curr_off - (( curr_off - 1) % tab_width ) - 1
    if prev_pos < 0:
        prev_pos = 0
    #print "Line: %d, %d, %d" % ( curr_line, curr_off, prev_pos  )
    prev_pos_iter = buffer.get_iter_at_line_offset( curr_line, prev_pos )
    buffer.begin_user_action()
    buffer.delete(prev_pos_iter, curr_iter)
    buffer.end_user_action()
    pass

#Helper classes
def set_hex_color( CNTX, hex_color ):
    red = int('0x'+hex_color[0:2],16)
    green = int('0x'+hex_color[2:4],16)
    blue = int('0x'+hex_color[4:6],16)
    CNTX.set_source_rgb( red, green, blue)
    pass

#<type 'gtk.gdk.CairoContext'>
#Foo.func = func
#gtk.gdk.CairoContext.set_hex_color = set_hex_color
#print type(gtk.gdk.CairoContext)
class Editor:

    def build_table(self):
        table = gtk.TextTagTable()
        tag = gtk.TextTag("comment")
        tag.set_property("style", pango.STYLE_ITALIC )
        table.add( tag )
        return table

    def __init__(self):
        buffer = gtksourceview.Buffer()
        buffer.get_curr_iter = new.instancemethod(get_curr_iter, buffer, buffer.__class__)
        buffer.get_curr_line = new.instancemethod(get_curr_line, buffer, buffer.__class__)
        buffer.get_curr_line_iters = new.instancemethod(get_curr_line_iters, buffer, buffer.__class__)

        mgr = gtksourceview.style_scheme_manager_get_default()
        style_scheme = mgr.get_scheme('mystyle')
        if style_scheme:
            buffer.set_style_scheme(style_scheme)

        #buffer.connect( "changed", self.on_buffer_changed )
        manager = gtksourceview.LanguageManager()
        language = manager.get_language("python")
        buffer.set_language(language)
        self.text_view = gtksourceview.View(buffer)

        self.text_view.set_show_line_numbers(True)
        self.text_view.set_property( "insert-spaces-instead-of-tabs", True )
        self.text_view.set_property( "highlight-current-line", True )
        self.text_view.set_property( "indent-on-tab", True )
        self.text_view.set_property( "tab-width", 4 )
        self.text_view.set_property( "auto-indent", True )
        self.text_view.set_property( "smart-home-end", True )
        self.text_view.comment = new.instancemethod(comment, self.text_view, self.text_view.__class__)
        self.text_view.uncomment = new.instancemethod(uncomment, self.text_view, self.text_view.__class__)
        self.text_view.delete_line = new.instancemethod(delete_line, self.text_view, self.text_view.__class__)
        self.text_view.insert_indented = new.instancemethod(insert_indented, self.text_view, buffer.__class__)
        self.text_view.back_indented = new.instancemethod(back_indented, self.text_view, buffer.__class__)

        self.text_view.font_size = 10
        self.font_size = 10
        font = pango.FontDescription( "Monospace %d" % self.font_size )
        print font.get_size()
        
        self.text_view.modify_font(font)
        self.text_view.connect('key-press-event', self.key_press_event)
        self.TAB_KEYVAL = gtk.gdk.keyval_from_name("Tab")
        self.KP_ENTER = gtk.gdk.keyval_from_name("KP_Enter")
        self.RETURN = gtk.gdk.keyval_from_name("Return")
        self.BACK = gtk.gdk.keyval_from_name("BackSpace")
        self.TEST = gtk.gdk.keyval_from_name("Control_L")
        pass

    def on_buffer_changed( self, buffer ):
        print buffer.get_curr_line()
        pass

    #Editor custiminzation. The idea is to take the TextView and make it work like a good editor.
    #Tab: nsert four spaces
    #Enter: Open new line. Start at beginning of previous one.
    def key_press_event(self, text_view, event):
        #print "KEY: %s, %d" % ( gtk.gdk.keyval_name(event.keyval), event.keyval)

        # Aqui vamos a tener que usar la magia para que esto funciona con
        # identacion (1) y softspaces (2)
        #print keyname
        buffer = text_view.get_buffer()
        if ( len(buffer.get_selection_bounds()) <> 0 ):
            return False

        if event.keyval in (self.RETURN, self.KP_ENTER):
            text_view.insert_indented()
            return True
        elif event.keyval == self.BACK:
            line = get_curr_line( buffer )
            pos = buffer.get_curr_iter().get_line_offset()
            trim_len = len(line[:pos].strip())
            if ( pos == 0 or trim_len <> 0 ):
                # Space only line
                return False

            text_view.back_indented()
            return True
        #~ elif event.keyval == self.TEST:
            #~ #Style test:
            #~ print "Test"
            #~ s, e = buffer.get_curr_line_iters()

            #~ buffer.apply_tag_by_name("comment", s, e )
            #~ return False


        #if keyname in ['Return','KP_Enter']:
        #    buffer = text_view.get_buffer()
        #    iter = buffer.get_iter_at_mark(buffer.get_insert())
        #    return self.follow_if_link(text_view, iter)
        #print 'key_press_event'
        return False

class Screen(gtk.DrawingArea):
    # Draw in response to an expose-event
    __gsignals__ = { "expose-event": "override" }

    def __init__(self, _exception_notifier):
        gtk.DrawingArea.__init__(self)
        self.exception_notifier = _exception_notifier
        self.drawing_code = None
        self.drawing_str_code = None

    # Handle the expose-event by drawing
    def do_expose_event(self, event):
        # Create the cairo context
        cr = self.window.cairo_create()
        cr.rectangle(event.area.x, event.area.y,
                event.area.width, event.area.height)
        cr.clip()

        alloc = self.get_allocation()
        self.internal_draw(cr, alloc.width, alloc.height )

    def internal_draw(self, CNTX, W, H ):
        global GLOBAL_COLOR, GLOBAL_COLOR_NORM
        COLOR = GLOBAL_COLOR_NORM
        try:
            if self.drawing_code != None:
                exec self.drawing_code in locals()
                sys.stdout = self.exception_notifier
            else:
                if self.drawing_str_code != None:
                    exec self.drawing_str_code in locals()
            sys.stdout = self
        except Exception, inst:
            sys.stdout = self
            tb = sys.exc_info()[2]
            print_tb(tb)
            tbList = extract_tb(tb)
            lastCall = tbList[len(tbList)-1]
            lineNo = lastCall[1]
            self.exception_notifier.set_status_message( 'Line ' + str(lineNo) + ': ' + str(inst) )
            #print( 'Line ' + str(lineNo) + ': ' + str(inst) )
            self.exception_notifier.move_cursor(lineNo,1)


    def set_compiled_code( self, code ):
        try:
            self.drawing_code = code
            sys.stdout = self.exception_notifier
            self.queue_draw()
        except:
            traceback_to_str(sys.exc_info()[2])

    def set_str_code( self, code ):
        try:
            self.drawing_str_code = code
            self.queue_draw()
        except:
            traceback_to_str(sys.exc_info()[2])

    def write( self, str ):
        sys.__stdout__.write( str )
        pass

class WorkArea(gtk.HPaned):
    def __init__(self, notifier):
        gtk.HPaned.__init__(self)

        #~ self.connect("expose-event", self.expose)
        #~ self.connect("size-request", self.size_request)
        self.connect("size-allocate", self.size_allocate)
        #~ self.connect("realize", self.show)

        self.left_side = gtk.VPaned()

        self.editor = Editor()
        self.allocating = False

        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        #scrolled_window.add_with_viewport(self.editor.text_view)
        scrolled_window.add(self.editor.text_view)

        self.left_side.add1(scrolled_window)

        # Console stuff...
        self.console_scroller = gtk.ScrolledWindow()
        self.console = gtk.TextView(gtk.TextBuffer())
        self.end_mark = self.console.get_buffer().create_mark("end-of-buffer", self.console.get_buffer().get_end_iter(), False )
        self.console.set_editable( False )
        self.console_scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.console_scroller.add(self.console)

        self.left_side.add2(self.console_scroller)

        self.add1( self.left_side )

        self.screen = Screen(notifier)

        self.add2( self.screen )

        self.saved = False # Is this work area saved?
        self.linked_file = None # Asociated  file
        pass

    def reset_areas(self):
        a = self.get_allocation()
        self.set_position( a.width / 2 )
        self.left_side.set_position( ( a.height / 4 ) * 3 )
        pass

    def size_allocate(self, widget, allocation):
        if self.allocating:
            return
        self.allocating = True
        self.reset_areas()
        pass

    def console_print( self, str ):
        #~ adj = self.console_scroller.get_vadjustment()
        #~ sys.__stdout__.write( "adj: %d\n" % ( adj.upper ) )
        self.console.get_buffer().insert_at_cursor( str )
        #~ adj = self.console_scroller.get_vadjustment()
        #~ sys.__stdout__.write( "adj2: %d\n" % ( adj.upper ) )
        #~ adj.set_value(adj.upper)
        pass

    def queue_console_draw(self):
        self.console.scroll_to_mark(self.end_mark, 0, True, 0.0, 1.0)
        self.console.queue_draw()
        pass

    def console_scroll_to_bottom(self):
        self.console.scroll_to_mark(self.end_mark, 0, True, 0.0, 1.0)
        pass

class MainWindow:
    def __init__(self, start_path):
        def build_actions():
            # Create an accelerator group
            accelgroup = gtk.AccelGroup()
            # Add the accelerator group to the toplevel window
            # window.add_accel_group(accelgroup) # do  in caller
            # Create an ActionGroup named ActionExample
            actiongroup = gtk.ActionGroup('ActionExample')

            action_list = [
            #     name, label, tooltip, stock_id, short-label, action, shortcut
                ( 'add_page_action', '_Add page', 'Add page', None, '_Add page',  self.add_page_action, None ),
                ( 'refresh_action', '_Refresh', 'Refresh', None, '_Refresh',  self.refresh_action, 'F5' ),
                # File ...
                ( 'new_action', '_New', 'New', gtk.STOCK_NEW, '_New',  self.new_action, None ),
                ( 'open_action', '_Open', 'Open', gtk.STOCK_OPEN, '_Open',  self.open_action, None ),
                ( 'save_action', '_Save', 'Save', gtk.STOCK_SAVE, '_Save',  self.save_action, None ),
                ( 'save_as_action', 'Save _As', 'Save As', gtk.STOCK_SAVE_AS, 'Save _As',  self.save_as_action, None ),
                ( 'exit_action', 'E_xit', 'Exit', gtk.STOCK_QUIT, 'E_xit',  self.exit_action, None ),
                #Edit
                ( 'undo_action', 'Undo', 'Undo', gtk.STOCK_UNDO, 'Undo',  self.undo_action, "<control>Z" ),
                ( 'redo_action', 'Redo', 'Redo', gtk.STOCK_REDO, 'Redo',  self.redo_action, "<control>Y" ),
                ( 'cut_action', 'Cut', 'Cut', gtk.STOCK_CUT, 'Cut',  self.cut_action, None ),
                ( 'copy_action', 'Copy', 'Copy', gtk.STOCK_COPY, 'Copy',  self.copy_action, None ),
                ( 'paste_action', 'Paste', 'Paste', gtk.STOCK_PASTE, 'Paste',  self.paste_action, None ),
                ( 'indent_action', 'Indent', 'Indent', gtk.STOCK_INDENT, 'Indent',  self.indent_action, None ),
                ( 'unindent_action', 'Unindent', 'Unindent', gtk.STOCK_UNINDENT, 'Unindent',  self.paste_action, None ),
                ( 'comment_action', 'Comment', 'Comment', None, 'Comment',  self.comment_action, "<control>K" ),
                ( 'uncomment_action', 'Uncomment', 'Uncomment', None, 'Uncomment',  self.uncomment_action, "<control><shift>K" ),
                ( 'del_line_action', 'Delete line', 'Delete line', None, 'Delete line',  self.del_line_action, "<control>L" ),
                ( 'enlarge_action', 'Enlarge', 'Enlarge', None, 'Enlarge Text',  self.enlarge_action, "<Control>E" ),
                ( 'reduce_action', 'Reduce', 'Reduce', None, 'Reduce Text',  self.reduce_action, "<Control><Shift>E" ),

                #Help
                ( 'about_action', '_About', 'About', gtk.STOCK_ABOUT, '_About',  self.about_action, None ),
                ( 'tutorial_action', 'Cairo Tutorial for Python Programmers', 'Cairo Tutorial for Python Programmers', None, 'Tutorial',  self.tutorial_action, None ),
                ( 'resources_action', 'Pycairo resources', 'Pycairo resources', None, 'Resources',  self.resources_action, None ),
                ( 'api_action', 'API reference', 'API reference', None, 'API',  self.api_action, None ),
                ( 'color_reference_action', 'Color reference', 'Color reference', None, 'Color',  self.color_reference_action, None ),
                ( 'quick_ref', 'Python 2.5 Quick Reference', 'Python 2.5 Quick Reference', None, 'QuickRef',  self.quick_ref_action, None ),
                # Buffer
                ( 'export_action', '_Export to PNG', 'Export PNG', None, '_Export PNG',  self.do_export_png, None ),
                ( 'dark_scheme_action', '_Dark shceme', 'Dark shceme', None, '_Dark',  self.dark_scheme_action, None ),
                ( 'close_buff_action', '_Close buffer', 'Close buffer', gtk.STOCK_CLOSE, '_Close',  self.close_buffer_action, None ),

            ]

            for a in action_list:
                # Create an action for quitting the program using a stock item
                action = gtk.Action( a[0], a[1], a[2], a[3]  )
                action.set_property('short-label', a[4])
                # Connect a callback to the action
                action.connect('activate', a[5])
                # Add the action to the actiongroup with an accelerator
                if a[6] == None:
                    if a[3] == None:
                        actiongroup.add_action( action )
                    else:
                        actiongroup.add_action_with_accel( action, None )
                else:
                    actiongroup.add_action_with_accel( action, a[6] )

                # Have the action use accelgroup
                action.set_accel_group(accelgroup)
                # Connect the accelerator to the action
                action.connect_accelerator()

            return ( accelgroup, actiongroup )

        def build_main_window():
            def build_notebook():
                notebook = gtk.Notebook()
                notebook.set_tab_pos(gtk.POS_TOP)
                return notebook

            def build_menu( accel_group, action_group ):

                def get_examples_menu():
                    #samples menu
                    samples_menu = gtk.Menu()
                    sample_dir = os.path.join(start_path,"2d-samples")
                    reg = re.compile("^.*\.py$")
                    for i in filter( lambda x: reg.match(x.lower()), os.listdir(sample_dir) ):
                        # add file name to menu
                        menu_item = gtk.MenuItem(i)
                        menu_item.connect("activate", self.load_sample_action, os.path.join( sample_dir, i ) )
                        samples_menu.append(menu_item)

                    samples_menu_item = gtk.MenuItem("_Samples")
                    samples_menu_item.set_submenu(samples_menu)
                    return samples_menu_item

                def get_tutorial_menu():
                    #samples menu
                    samples_menu = gtk.Menu()
                    sample_dir = os.path.join(start_path,"tutorial")
                    reg = re.compile("^.*\.py$")
                    for i in filter( lambda x: reg.match(x.lower()), os.listdir(sample_dir) ):
                        # add file name to menu
                        menu_item = gtk.MenuItem(i)
                        menu_item.connect("activate", self.load_sample_action, os.path.join( sample_dir, i ) )
                        samples_menu.append(menu_item)

                    samples_menu_item = gtk.MenuItem("_Tutorial")
                    samples_menu_item.set_submenu(samples_menu)
                    return samples_menu_item


                menu_bar = gtk.MenuBar()

                #File
                file_menu = gtk.Menu()
                file_menu.append( action_group.get_action('new_action').create_menu_item() )
                file_menu.append( action_group.get_action('open_action').create_menu_item() )
                file_menu.append( action_group.get_action('save_action').create_menu_item() )
                file_menu.append( action_group.get_action('save_as_action').create_menu_item() )
                file_menu.append( get_examples_menu() )
                file_menu.append( get_tutorial_menu() )
                file_menu.append( action_group.get_action('exit_action').create_menu_item() )

                file_menu_item = gtk.MenuItem("_File")
                file_menu_item.set_submenu( file_menu )

                #edit
                edit_menu = gtk.Menu()
                edit_menu.append( action_group.get_action('undo_action').create_menu_item() )
                edit_menu.append( action_group.get_action('redo_action').create_menu_item() )
                edit_menu.append( action_group.get_action('cut_action').create_menu_item() )
                edit_menu.append( action_group.get_action('copy_action').create_menu_item() )
                edit_menu.append( action_group.get_action('paste_action').create_menu_item() )
                #~ edit_menu.append( action_group.get_action('indent_action').create_menu_item() )
                #~ edit_menu.append( action_group.get_action('unindent_action').create_menu_item() )
                edit_menu.append( action_group.get_action('comment_action').create_menu_item() )
                edit_menu.append( action_group.get_action('uncomment_action').create_menu_item() )
                edit_menu.append( action_group.get_action('del_line_action').create_menu_item() )
                
                # Buffer
                edit_menu.append( action_group.get_action('enlarge_action').create_menu_item() )
                edit_menu.append( action_group.get_action('reduce_action').create_menu_item() )

                edit_menu_item = gtk.MenuItem("_Edit")
                edit_menu_item.set_submenu(edit_menu)

                #canvas menu
                canvas_menu = gtk.Menu()
                canvas_menu.append( action_group.get_action('refresh_action').create_menu_item() )

                canvas_menu_item = gtk.MenuItem("_Canvas")
                canvas_menu_item.set_submenu(canvas_menu)

                #buffer menu
                self.buffer_menu = gtk.Menu()
                self.buffer_menu.append( action_group.get_action('export_action').create_menu_item() )
                #~ self.buffer_menu.append( action_group.get_action('dark_scheme_action').create_menu_item() )
                self.buffer_menu.append( action_group.get_action('close_buff_action').create_menu_item() )

                buffer_menu_item = gtk.MenuItem("_Buffer")
                buffer_menu_item.set_submenu(self.buffer_menu)

                #Help menu
                help_menu = gtk.Menu()
                help_menu.append( action_group.get_action('quick_ref').create_menu_item() )
                help_menu.append( action_group.get_action('tutorial_action').create_menu_item() )
                help_menu.append( action_group.get_action('resources_action').create_menu_item() )
                help_menu.append( action_group.get_action('api_action').create_menu_item() )
                help_menu.append( action_group.get_action('color_reference_action').create_menu_item() )
                help_menu.append( action_group.get_action('about_action').create_menu_item() )

                help_menu_item = gtk.MenuItem("_Help")
                help_menu_item.set_submenu(help_menu)

                # Bar menu
                menu_bar.append(file_menu_item)
                menu_bar.append(edit_menu_item)
                menu_bar.append(canvas_menu_item)
                menu_bar.append(buffer_menu_item)
                menu_bar.append(help_menu_item)

                return menu_bar


            def build_clipboard():
                return gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)

            accel_group, action_group = build_actions()

            window = gtk.Window(gtk.WINDOW_TOPLEVEL)
            window.add_accel_group(accel_group)

            window.set_size_request(640, 480)
            window.set_title("PyPreview")
            window.connect("delete_event", lambda w,e: gtk.main_quit())

            vbox = gtk.VBox(False, 0)

            self.menu = build_menu(accel_group, action_group)
            self.clipboard = build_clipboard()
            self.clipped_text = None

            vbox.pack_start( self.menu, False, False, 2)

            self.status_bar = gtk.Statusbar()
            vbox.pack_end(self.status_bar, False, False)
            self.context_id = self.status_bar.get_context_id("status_msg")
            self.last_msg_id = None

            self.notebook = build_notebook()
            vbox.pack_end( self.notebook, True, True, 2)

            window.add(vbox)

            return window

        self.window = build_main_window()
        self.window.show_all()

        self.window.show()

    def build_work_area(self):
        result = WorkArea(self)
        result.show_all()
        return result

    def set_status_message( self, _msg ):
        if self.last_msg_id != None:
            self.status_bar.remove(self.context_id, self.last_msg_id)
        self.last_msg_id = self.status_bar.push( self.context_id, _msg )

    # TODO: Aborting!
    def move_cursor( self, _lineno, _offset ):
        print _lineno, _offset
        try:
            buffer = self.get_current_buffer()
            buffer.place_cursor( buffer.get_iter_at_line_offset(_lineno - 1, _offset - 1) )
        except:
            pass

    def get_file_name(self, dlg_name ):
        filew = gtk.FileChooserDialog(dlg_name, self.window, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        result = None
        filew.set_default_response(gtk.RESPONSE_OK)
        response = filew.run()
        if response == gtk.RESPONSE_OK:
            result =  filew.get_filename()
        else:
            result = None
        filew.destroy()
        return result

    def get_current_buffer( self ):
        page = self.notebook.get_nth_page( self.notebook.get_current_page() )
        buffer = page.editor.text_view.get_buffer()
        return buffer

    def get_current_view( self ):
        page = self.notebook.get_nth_page( self.notebook.get_current_page() )
        view = page.editor.text_view
        return view

    def get_current_text( self ):
        buffer = self.get_current_buffer()
        bounds = buffer.get_bounds()
        text = buffer.get_text( bounds[0], bounds[1] )
        return text

    def get_current_work_area( self ):
        page = self.notebook.get_nth_page( self.notebook.get_current_page() )
        return page

    def get_current_canvas( self ):
        page = self.notebook.get_nth_page( self.notebook.get_current_page() )
        #~ return page.get_child2() # TODO: CHange this
        return page.screen

    def set_current_page_title( self, name ):
        page = self.notebook.get_nth_page( self.notebook.get_current_page() )
        self.notebook.set_tab_label_text( page, name )
        pass

    def refresh_canvas( self ):
        try:
            text = self.get_current_text()
            str_code = text.replace( '\r\n', '\n' )
            compiled = compile( str_code, '<string>', 'exec')
            self.get_current_canvas().set_compiled_code( compiled )
            #self.get_current_work_area().queue_console_draw()
            #~ self.get_current_work_area().console_scroll_to_bottom()
            #self.get_current_canvas().set_str_code( str_code )
            self.set_status_message( "OK" )
        except exceptions.SyntaxError, inst:
            print inst
            self.set_status_message( "Syntax error at line: " + str(inst.lineno) + ', position: ' + str(inst.offset) )
            self.move_cursor( inst.lineno, inst.offset )
        except:
            print "Unexpected error:", sys.exc_info()[0]

    def new_document(self):
        work_area = self.build_work_area()
        if work_area.linked_file is None:
            label = gtk.Label("<Untitled>")
        else:
            label = gtk.Label(work_area.linked_file)
        idx = self.notebook.append_page( work_area, label)
        self.notebook.set_current_page(idx)
        buffer = self.get_current_buffer()
        buffer.insert( buffer.get_start_iter(), DEF_CAIRO_CODE )
        self.refresh_buffer_menu()
        #~ work_area.show()
        #~ work_area.reset_areas()
        pass

    def ask_file_open(self):
        filew = gtk.FileChooserDialog("Open Script", self.window, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        result = None
        filew.set_default_response(gtk.RESPONSE_OK)
        response = filew.run()
        if response == gtk.RESPONSE_OK:
            result =  filew.get_filename()
        else:
            result = None
        filew.destroy()
        return result

    def open_document(self):
        name = self.ask_file_open()
        if name == None:
            return
        f = open(name)

        _, file_name = os.path.split(name)

        label_name = file_name

        label = gtk.Label(label_name)
        idx = self.notebook.append_page( self.build_work_area(), label)
        self.notebook.set_current_page(idx)
        buffer = self.get_current_buffer()

        buffer.insert( buffer.get_start_iter(), reduce(lambda n,m:n+m, f.readlines()) )
        f.close() # TODO: try/catch
        self.get_current_work_area().linked_file = name
        self.refresh_buffer_menu()
        pass

    def save(self, save_as_mode):
        work_area = self.get_current_work_area()

        if ( work_area.linked_file == None or save_as_mode ):
            name = self.get_file_name("Save File")
            if name == None:
                return

            work_area.linked_file = name

        buffer = self.get_current_buffer()
        script = buffer.get_text( buffer.get_start_iter(), buffer.get_end_iter() )
        f = open(work_area.linked_file, 'w')
        f.write( script ) # TODO: Check ok
        f.close()
        _, file_name = os.path.split(name)
        self.set_current_page_title( file_name )
        self.refresh_buffer_menu()
        pass

    def refresh_buffer_menu(self):
        for child in self.buffer_menu.get_children()[2:]:
            self.buffer_menu.remove(child)

        for tab_num in range(0,self.notebook.get_n_pages()):
            if self.notebook.get_nth_page(tab_num).linked_file is None:
                item = gtk.MenuItem( '<Untitled>' )
            else:
                item = gtk.MenuItem( self.notebook.get_nth_page(tab_num).linked_file )
            item.connect( 'activate', self.select_buffer_action, tab_num )
            self.buffer_menu.append(item)

        self.buffer_menu.show_all()
        pass

    #Actions ...
    def add_page_action(self, widget):
        label = gtk.Label("Editor...")
        self.notebook.append_page( self.build_work_area(), label)
        pass

    def refresh_action(self, widget):
        self.refresh_canvas()
        pass

    def new_action(self, widget):
        self.new_document()
        pass

    def open_action(self, widget):
        self.open_document()
        pass

    def save_action(self, widget):
        self.save( False )
        pass

    def save_as_action(self, widget):
        self.save(  True )
        pass

    def exit_action(self, widget):
        gtk.main_quit()
        pass

    def load_sample_action(self, widget, name ):
        work_area = self.build_work_area()
        buffer = work_area.editor.text_view.get_buffer()
        f = open(name)
        code = reduce(lambda n,m:n+m, f.readlines())
        buffer.insert( buffer.get_start_iter(), code )
        f.close() # TODO: try/catch
        work_area.linked_file = name
        _, file_name = os.path.split(name)
        idx = self.notebook.append_page( work_area, gtk.Label( file_name ) )
        self.notebook.set_current_page(idx)
        self.refresh_buffer_menu()
        pass

    def about_action(self, widget):
        about = gtk.Dialog("About PyPreview", self.window, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK))
        about.set_default_response(gtk.RESPONSE_OK)

        vbox = about.vbox

        notebook = gtk.Notebook()
        notebook.set_scrollable(True)
        notebook.set_border_width(4)
        notebook.set_tab_pos(gtk.POS_BOTTOM)
        vbox.pack_start(notebook)

        tab_vbox = gtk.VBox()

        def add_label(text, padding_x=0, padding_y=0):
            label = gtk.Label(text)
            label.set_property('use-markup', True)
            label.set_padding(padding_x, padding_y)
            label.set_justify(gtk.JUSTIFY_CENTER)
            tab_vbox.pack_start(label)

        #add_label('<span size="xx-large" weight="bold">Gaphor</span>')
        add_label('<span weight="bold">version %s</span>' % '0.2')
        add_label('<span variant="smallcaps">Simple graphics-oriented python development environment</span>', 8, 8)
        add_label('<span size="small">Copyright (c) 2007-2008 Omar Gomez</span>', 8, 8)
        #vbox.pack_start(gtk.HSeparator())
        notebook.append_page(tab_vbox, gtk.Label('About'))

        tab_vbox = gtk.VBox()

        add_label('This software is published\n'
                  'under the terms of the\n'
                  '<span weight="bold">GNU General Public License v2</span>.\n'
                  'See the COPYING file for details.', 0, 8)
        notebook.append_page(tab_vbox, gtk.Label('License'))

        tab_vbox = gtk.VBox()

        add_label('PyPreview is written by:\n'
                  'Omar Gomez - omar.gomez@gmail.com')
        add_label('')
        notebook.append_page(tab_vbox, gtk.Label('Authors'))

        vbox.show_all()
        about.run()
        about.destroy()
        pass

    def close_buffer_action(self, widget):
        self.notebook.remove_page(self.notebook.get_current_page())
        self.refresh_buffer_menu()
        pass

    def select_buffer_action( self, widget, idx ):
        self.notebook.set_current_page(idx)
        pass

    def cut_action( self, widget ):
        # TODO: validate current buffer (cut, copy, paste)
        view = self.get_current_view()
        buffer = self.get_current_buffer()
        buffer.cut_clipboard( self.clipboard, view.get_editable() )
        pass

    def copy_action( self, widget ):
        buffer = self.get_current_buffer()
        buffer.copy_clipboard( self.clipboard )
        pass

    def paste_action( self, widget ):
        buffer = self.get_current_buffer()
        view = self.get_current_view()
        buffer.paste_clipboard( self.clipboard, None, view.get_editable() )
        pass

    def undo_action( self, widget ):
        buffer = self.get_current_buffer()
        if buffer.can_undo():
            buffer.undo()
        pass

    def redo_action( self, widget ):
        buffer = self.get_current_buffer()
        if buffer.can_redo():
            buffer.redo()
        pass

    def indent_action( self, widget ):
        view = self.get_current_view()
        view.indent()
        pass

    def comment_action( self, widget ):
        view = self.get_current_view()
        view.comment()
        pass

    def uncomment_action( self, widget ):
        view = self.get_current_view()
        view.uncomment()
        pass

    def del_line_action( self, widget ):
        view = self.get_current_view()
        view.delete_line()
        pass

    def tutorial_action( self, widget ):
        webbrowser.open("http://www.tortall.net/mu/wiki/CairoTutorial")
        pass

    def resources_action( self, widget ):
        webbrowser.open("http://cairographics.org/pycairo/resources/")
        pass

    def api_action( self, widget ):
        webbrowser.open("http://www.cairographics.org/manual/cairo-cairo-t.html")
        pass

    def color_reference_action( self, widget ):
        webbrowser.open("http://www.farb-tabelle.de/en/table-of-color.htm")
        pass

    def quick_ref_action( self, widget ):
        webbrowser.open("http://rgruet.free.fr/PQR25/PQR2.5.html")
        pass

    def dark_scheme_action( self, widget ):
        mgr = gtksourceview.style_scheme_manager_get_default()
        style_scheme = mgr.get_scheme('oblivion')
        if style_scheme:
            buffer = self.get_current_buffer()
            if buffer:
                buffer.set_style_scheme(style_scheme)
        pass

    def resize_font( self, factor ):
        textview = self.get_current_view()
        textview.font_size *= factor
        font = pango.FontDescription( "Monospace %d" % int(textview.font_size ) )
        textview.modify_font(font)
        pass
    
    def enlarge_action( self, widget):
        self.resize_font( 1.2 )
        pass

    def reduce_action( self, widget):
        self.resize_font( 0.8 )
        pass

    def get_export_file(self, _filter ):
        filew = gtk.FileChooserDialog("Export to PNG", self.window, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        result = None
        filew.set_default_response(gtk.RESPONSE_OK)
        response = filew.run()
        if response == gtk.RESPONSE_OK:
            result =  filew.get_filename()
        else:
            result = None
        filew.destroy()
        return result
        
    def do_export_png(self, widget):
        
        global GLOBAL_COLOR
        
        def draw_to_context(self, CNTX, W, H, COLOR, drawing_code ):
            try:
                print locals()
                exec drawing_code in locals()
            except Exception, inst:
                tb = sys.exc_info()[2]
                print_tb(tb)
                tbList = extract_tb(tb)
                lastCall = tbList[len(tbList)-1]
                lineNo = lastCall[1]
                self.set_status_message( 'Line ' + str(lineNo) + ': ' + str(inst) )
                self.move_cursor(lineNo,1)
        
        file = self.get_export_file('*.png')

        if file == None:
            return
            
        #~ alloc = self.get_allocation()
        alloc = self.get_current_canvas().get_allocation()
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, alloc.width, alloc.height)
        ctx = cairo.Context(surface)

        try:
            text = self.get_current_text()
            str_code = text.replace( '\r\n', '\n' )
            compiled = compile( str_code, '<string>', 'exec')
            draw_to_context( self, ctx, alloc.width, alloc.height, GLOBAL_COLOR, compiled )
            self.set_status_message( "OK" )
            surface.write_to_png(file)
        except exceptions.SyntaxError, inst:
            print inst
            self.set_status_message( "Syntax error at line: " + str(inst.lineno) + ', position: ' + str(inst.offset) )
            self.move_cursor( inst.lineno, inst.offset )
        except:
            print "Unexpected error:", sys.exc_info()[0]
        
    def print_proxy(self, str):
        # Imprime en la console
        work_area = self.get_current_work_area()
        work_area.console_print( str )
        pass

    def write(self, str):
        # Imprime en la console
        work_area = self.get_current_work_area()
        work_area.console_print( str )
        work_area.console_scroll_to_bottom()
        pass

if __name__ == "__main__":
    start_path = os.path.abspath(os.path.dirname(sys.argv[0]))
    icon = os.path.join(start_path, "pypreview64.")
    #~ gtk.window_set_default_icon_from_file(icon + "png")
    win = MainWindow(start_path)
    gtk.main()
